package com.smile.cloud.ossConfig.oss.aliyun;

import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.auth.DefaultCredentials;
import com.aliyun.oss.model.*;

import com.smile.cloud.ossConfig.FileUtils;
import com.smile.cloud.ossConfig.oss.OssClient;
import com.smile.cloud.ossConfig.oss.exception.ClientException;
import com.smile.cloud.ossConfig.oss.exception.OssException;
import com.smile.cloud.ossConfig.oss.exception.ServiceException;
import com.smile.cloud.ossConfig.oss.model.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;

@Component
public class AliyunOssClient implements OssClient {
    private OSS ossClient;
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;
    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;
    @Value("${aliyun.oss.accessKeySecret}")
    private String secretAccessKey;

    protected OSS aliyunOssClient() {
        if (this.ossClient == null) {
            this.ossClient = new OSSClientBuilder().build(this.endpoint, this.accessKeyId, this.secretAccessKey);
        }
        return this.ossClient;
    }

    @Override
    public void shutdown() {
        this.ossClient.shutdown();
    }

    @Override
    public URL generatePresignedUrl(final OssPresignedUrlRequest request) throws Exception {
        try {
            final GeneratePresignedUrlRequest req = this.convert(request);
            return this.aliyunOssClient().generatePresignedUrl(req);
        } catch (Exception ex) {
            throw this.HandleException(ex);
        }
    }

    @Override
    public OssObjectResult getObject(final String bucketName, final String key) throws Exception {
        try {
            final OSSObject ossObject = this.aliyunOssClient().getObject(bucketName, key);
            return this.convert(ossObject);
        } catch (Exception ex) {
            throw this.HandleException(ex);
        }
    }

    @Override
    public OssStreamResponse putObject(final String bucketName, final String key, final InputStream stream) throws Exception {
        try {
            final ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(FileUtils.getContentType(FileUtils.getExtension(key)));
            final PutObjectResult res = this.aliyunOssClient().putObject(bucketName, key, stream, metadata);
            return new OssStreamResponse(res.getCallbackResponseBody(), res.getRequestId(), res.getClientCRC(), res.getServerCRC());
        } catch (Exception ex) {
            throw this.HandleException(ex);
        }
    }

    @Override
    public OssStreamResponse putObject(String bucketName, String key, InputStream stream, ObjectMetadata metadata) throws Exception {
        try {
            final PutObjectResult res = this.aliyunOssClient().putObject(bucketName, key, stream, metadata);
            return new OssStreamResponse(res.getCallbackResponseBody(), res.getRequestId(), res.getClientCRC(), res.getServerCRC());
        } catch (Exception ex) {
            throw this.HandleException(ex);
        }
    }

    @Override
    public OssStreamResponse putObject(final String bucketName, final String key, final InputStream stream, final String callbackUrl, final String callbackBody, final Map<String, String> userVariables) throws Exception {
        try {
            final PutObjectRequest req = new PutObjectRequest(bucketName, key, stream);
            final Callback callback = new Callback();
            callback.setCallbackUrl(callbackUrl);
            callback.setCallbackBody(callbackBody);
            callback.setCalbackBodyType(Callback.CalbackBodyType.JSON);
            userVariables.forEach((k, v) -> callback.addCallbackVar("x:" + k, v));
            req.setCallback(callback);
            final PutObjectResult res = this.aliyunOssClient().putObject(req);
            return new OssStreamResponse(res.getCallbackResponseBody(), res.getRequestId(), res.getClientCRC(), res.getServerCRC());
        } catch (Exception ex) {
            throw this.HandleException(ex);
        }
    }

    @Override
    public void deleteObject(final String bucketName, final String key) throws Exception {
        try {
            this.aliyunOssClient().deleteObject(bucketName, key);
        } catch (Exception ex) {
            throw this.HandleException(ex);
        }
    }

    @Override
    public void copyObject(final String srcBucketName, final String sourceKey, final String destinationBucketName, final String destinationKey) throws Exception {
        try {
            final CopyObjectRequest req = new CopyObjectRequest(srcBucketName, sourceKey, destinationBucketName, destinationKey);
            this.aliyunOssClient().copyObject(req);
        } catch (Exception ex) {
            throw this.HandleException(ex);
        }
    }

    @Override
    public OssObjectListResult listObjects(final String bucketName, final String prefix) throws Exception {
        try {
            final ObjectListing result = this.aliyunOssClient().listObjects(bucketName, prefix);
            return this.convert(result);
        } catch (Exception ex) {
            throw this.HandleException(ex);
        }
    }

    @Override
    public OssDeleteObjectsResult deleteObjects(final String bucketName, final List<String> keys) throws Exception {
        return this.deleteObjects(bucketName, keys, false);
    }

    @Override
    public OssDeleteObjectsResult deleteObjects(final String bucketName, final List<String> keys, final boolean quiet) throws Exception {
        try {
            final DeleteObjectsResult result = this.aliyunOssClient().deleteObjects(new DeleteObjectsRequest(bucketName).withKeys((List) keys).withQuiet(quiet));
            return this.convert(result);
        } catch (Exception ex) {
            throw this.HandleException(ex);
        }
    }

    @Override
    public void setEndpoint(final String endPoint) {
    }

    @Override
    public void switchCredentials(final Credentials creds) {
        final com.aliyun.oss.common.auth.Credentials aliyunCreds = (com.aliyun.oss.common.auth.Credentials) new DefaultCredentials(creds.getAccessKeyId(), creds.getAccessKeySecret(), creds.getSecurityToken());
        this.aliyunOssClient().switchCredentials(aliyunCreds);
    }

    private OssDeleteObjectsResult convert(final DeleteObjectsResult aliyunResult) {
        final OssDeleteObjectsResult result = new OssDeleteObjectsResult();
        result.setDeletedObjects(aliyunResult.getDeletedObjects());
        result.setEncodingType(aliyunResult.getEncodingType());
        result.setClientCRC(aliyunResult.getClientCRC());
        result.setRequestId(aliyunResult.getRequestId());
        result.setServerCRC(aliyunResult.getServerCRC());
        return result;
    }

    private OssObjectListResult convert(final ObjectListing objectListing) {
        final OssObjectListResult result = new OssObjectListResult();
        result.setBucketName(objectListing.getBucketName());
        result.setCommonPrefixes(objectListing.getCommonPrefixes());
        result.setDelimiter(objectListing.getDelimiter());
        result.setEncodingType(objectListing.getEncodingType());
        result.setMarker(objectListing.getMarker());
        result.setMaxKeys(objectListing.getMaxKeys());
        result.setNextMarker(objectListing.getNextMarker());
        if (objectListing.getObjectSummaries() != null) {
            final OssObjectSummary[] objectSummary = new OssObjectSummary[1];
            final OssObjectListResult ossObjectListResult = null;
            objectListing.getObjectSummaries().forEach(summary -> {
                objectSummary[0] = new OssObjectSummary();
                objectSummary[0].setBucketName(summary.getBucketName());
                objectSummary[0].setETag(summary.getETag());
                objectSummary[0].setKey(summary.getKey());
                objectSummary[0].setLastModified(summary.getLastModified());
                objectSummary[0].setSize(summary.getSize());
                objectSummary[0].setStorageClass(summary.getStorageClass());
                objectSummary[0].setOwner(new OssOwner(summary.getOwner().getId(), summary.getOwner().getDisplayName()));
                ossObjectListResult.addObjectSummary(objectSummary[0]);
                return;
            });
        }
        return result;
    }

    private OssObjectResult convert(final OSSObject ossObject) {
        final OssObjectResult result = new OssObjectResult();
        result.setBucketName(ossObject.getBucketName());
        result.setContent(ossObject.getObjectContent());
        result.setKey(ossObject.getKey());
        result.setMetadata(this.convert(ossObject.getObjectMetadata()));
        result.setClientCRC(ossObject.getClientCRC());
        result.setRequestId(ossObject.getRequestId());
        result.setServerCRC(ossObject.getServerCRC());
        return result;
    }

    private OssMetadata convert(final ObjectMetadata objMetaData) {
        final OssMetadata metadata = new OssMetadata();
        metadata.setUserMetadata(objMetaData.getUserMetadata());
        metadata.setMetadata(objMetaData.getRawMetadata());
        return metadata;
    }

    private GeneratePresignedUrlRequest convert(final OssPresignedUrlRequest request) throws Exception {
        final GeneratePresignedUrlRequest aliYunReq = new GeneratePresignedUrlRequest(request.getBucketName(), request.getKey(), this.convert(request.getMethod()));
        aliYunReq.setContentMD5(request.getContentMD5());
        aliYunReq.setContentType(request.getContentType());
        aliYunReq.setExpiration(request.getExpiration());
        aliYunReq.setProcess(request.getProcess());
        aliYunReq.setQueryParameter((Map) request.getQueryParameter());
        aliYunReq.setUserMetadata((Map) request.getUserMetadata());
        return aliYunReq;
    }

    private com.aliyun.oss.HttpMethod convert(HttpMethod httpMethod) throws Exception {
        switch (httpMethod) {
            case GET: {
                return com.aliyun.oss.HttpMethod.GET;
            }
            case POST: {
                return com.aliyun.oss.HttpMethod.POST;
            }
            case DELETE: {
                return com.aliyun.oss.HttpMethod.DELETE;
            }
            case HEAD: {
                return com.aliyun.oss.HttpMethod.HEAD;
            }
            case PUT: {
                return com.aliyun.oss.HttpMethod.PUT;
            }
            case OPTIONS: {
                return com.aliyun.oss.HttpMethod.OPTIONS;
            }
            default: {
                throw new Exception("Invalid HttpMethod value");
            }
        }
    }

    private Exception HandleException(final Exception ex) {
        if (ex instanceof ClientException) {
            return new ClientException(ex.getMessage(), ((ClientException) ex).getErrorCode(), ((ClientException) ex).getRequestId());
        }
        if (ex instanceof OSSException) {
            return new OssException(ex.getMessage(), ((OSSException) ex).getErrorCode(), ((OSSException) ex).getRequestId(), ((OSSException) ex).getHostId(), ((OSSException) ex).getHeader(), ((OSSException) ex).getResourceType(), ((OSSException) ex).getMethod(), ((OSSException) ex).getRawResponseError(), ex.getCause());
        }
        if (ex instanceof ServiceException) {
            return new ServiceException(ex.getMessage(), ((ServiceException) ex).getErrorCode(), ((ServiceException) ex).getRequestId(), ((ServiceException) ex).getHostId(), ((ServiceException) ex).getRawResponseError(), ex.getCause());
        }
        return ex;
    }
}
